/*
 * Copyright (c) 1997, 1998, 2003, 2006 Aleksandar Samardzic
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <assert.h>		/* for assert() function */
#include <stdlib.h>		/* for malloc() function */
#include <unistd.h>		/* for getopt() function */

#ifdef USE_OPENMP
#include <omp.h>
#endif

#ifdef USE_PTHREADS
#include <pthread.h>
#endif

#include "common.h"
#include "input.h"
#include "render.h"
#include "output.h"

#ifdef USE_PTHREADS
void           *Render(void *);
#endif

PScene          scene;
PImage          image;
PQueue          problems;
int             total,
                processed;

int
main(argc, argv)
     int             argc;
     char           *argv[];
{
	int             c;
	char           *pToken0,
	               *pToken1;
	DWord           blockSize[2] = { 16, 16 };
	Byte            maxRayDepth = 5;
	Bool            useGrid = FALSE;
	DWord           imageSize[2] = { 512, 512 };
	Word            seed = 0;
	Word            threadsCount = 1;
	char           *message =
	    "Tinos v1.2.0 - parallel ray tracer.\n"
	    "Copyright (c) 1997, 1998, 2003, 2006, Aleksandar Samardzic.\n"
	    "\n" "Usage: tinos [options] <nff-file >ppm-file\n"
	    "Options:\n" "  -b <w>x<h>   block size (default is 16x16)\n"
	    "  -d <n>       maximum ray depth (default is 5)\n"
	    "  -g           use grid (defult is bounding volume hierarchy)\n"
	    "  -h           display this help message\n"
	    "  -i <w>x<h>   image size (default is 512x512)\n"
	    "  -s <n>       seed for automatic hierarchy generation (default is 0)\n"
	    "  -t <n>       number of threads (default is 1)\n";
	int             i;

	/* NOTE: Tinos supports prismatic bounding volumes only and slab
	 * vectors must be unit vectors */
	PVector         slabVectors[3] =
	    { {1, 0, 0}, {0, 1, 0}, {0, 0, 1} };
	PSlabs          slabs = { 3, slabVectors };

	DWord           row,
	                col;
	PRectangle     *pProblem;
	char           *comment = "Generated by Tinos 1.2.0.";

	/* handle options */
	for (; (c = getopt(argc, argv, "b:d:hgi:s:t:")) != -1;)
		switch (c) {
		case 'b':
			/* set block size */
			pToken0 = strtok(optarg, "x");
			pToken1 = strtok(NULL, " ");
			if (pToken0 != NULL && pToken1 != NULL) {
				blockSize[X] = (DWord) atoi(pToken0);
				blockSize[Y] = (DWord) atoi(pToken1);
			}
			break;

		case 'd':
			/* set maximum ray depth */
			maxRayDepth = (Byte) atoi(optarg);
			break;

		case 'g':
			/* use grid as data access structure */
			useGrid = TRUE;
			break;

		case 'h':
			/* print help message */
			fprintf(stderr, message);
			exit(EXIT_FAILURE);

		case 'i':
			/* set image size */
			pToken0 = strtok(optarg, "x");
			pToken1 = strtok(NULL, " ");
			if (pToken0 != NULL && pToken1 != NULL) {
				imageSize[X] = (DWord) atoi(pToken0);
				imageSize[Y] = (DWord) atoi(pToken1);
			}
			break;

		case 's':
			/* set random generator seed for automatic
			 * hierarchy creation */
			seed = (Word) atoi(optarg);
			break;

		case 't':
			/* set number of threads */
			threadsCount = (Word) atoi(optarg);
			break;
		}

	/* initialize and load scene */
	Scene_Init(&scene);
	Scene_Serialize(&scene, &slabs, maxRayDepth, useGrid, seed);
	scene.window.width = imageSize[X], scene.window.height =
	    imageSize[Y];

	/* initialize image */
	Image_Init(&image, scene.window.width + 1,
		   scene.window.height + 1);

	/* initialize queue where problems to solve will be put */
	Queue_Init(&problems);

	/* decompose rendering task into set of problems and put problems
	 * into queue */
	for (total = 0, row = 0; row <= scene.window.height;
	     row += blockSize[Y])
		for (col = 0; col <= scene.window.width;
		     col += blockSize[X], total++) {
			pProblem = malloc(sizeof(PRectangle));
			assert(pProblem != NULL);
			pProblem->left = col, pProblem->bottom = row;
			pProblem->width =
			    (col + blockSize[X] <=
			     scene.window.width) ? blockSize[X] : scene.
			    window.width - col + 1;
			pProblem->height =
			    (row + blockSize[Y] <=
			     scene.window.height) ? blockSize[Y] : scene.
			    window.height - row + 1;
			Queue_Push(&problems, pProblem);
		}

#ifdef USE_OPENMP
	/* define number of threads for parallel execution */
	omp_set_num_threads(threadsCount);
#pragma omp parallel default(shared) private(processed, pProblem)
	{
#pragma omp for schedule(dynamic)
		for (processed = 0; processed < total; processed++) {
#pragma omp critical
			{
				/* fetch next problem from queue */
				pProblem = Queue_Pop(&problems);

				/* give feedback to user when starting to
				 * solve new problem */
				fprintf(stderr, "\rProcessing... %3d%%",
					100 * processed / total);
			}

			/* render part of image */
			Scene_Render(&scene, image.pPixels, pProblem);
			free(pProblem);
		}
	}
#endif

#ifdef USE_PTHREADS
	{
		pthread_mutex_t lock;
		pthread_t      *threads;
		pthread_attr_t  attributes;

		processed = 0;

		/* initialize mutex */
		pthread_mutex_init(&lock, NULL);

		/* create threads */
		pthread_attr_init(&attributes);
		pthread_attr_setdetachstate(&attributes,
					    PTHREAD_CREATE_JOINABLE);
		threads = malloc(threadsCount * sizeof(pthread_t));
		assert(threads != NULL);
		for (i = 0; i < threadsCount; i++)
			pthread_create(threads + i, &attributes, Render,
				       (void *) &lock);

		/* wait for threads to complete */
		for (i = 0; i < threadsCount; i++)
			pthread_join(threads[i], NULL);
		free(threads);

		/* destroy mutex */
		pthread_mutex_destroy(&lock);
	}
#endif

	/* clean scene */
	Scene_Clean(&scene);

	/* perform simple antialiasing according to SPD readme file */
	Image_SimpleBoxResample(&image);

	/* save and clean image */
	Image_Deserialize(&image, comment);
	Image_Clean(&image);

	/* clean problems queue */
	Queue_Clean(&problems);

	/* write completion message */
	fprintf(stderr, "\rCompleted.        \n");

	/* end program */
	exit(EXIT_SUCCESS);
}

#ifdef USE_PTHREADS
void           *
Render(lock)
     void           *lock;
{
	PRectangle     *pProblem;

	for (;;) {
		/* lock mutex */
		pthread_mutex_lock(lock);

		/* fetch next problem from queue */
		pProblem = Queue_Pop(&problems);

		/* terminate loop if there are no problems left in queue */
		if (pProblem == NULL) {
			pthread_mutex_unlock(lock);
			break;
		}

		/* give feedback to user when starting to solve new
		 * problem */
		processed++;
		fprintf(stderr, "\rProcessing... %3d%%",
			100 * processed / total);

		/* unlock mutex */
		pthread_mutex_unlock(lock);

		/* render part of image */
		Scene_Render(&scene, image.pPixels, pProblem);
		free(pProblem);
	}

	/* finish thread */
	pthread_exit(0);
}
#endif
